/*
 * MIT License
 *
 * Copyright (c) 2025 QmDeve
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * ===========================================
 * Project: QmBlurView
 * Created Date: 2025-10-21
 * Author: QmDeve
 * GitHub: https://github.com/QmDeve/QmBlurView
 *
 * Contributors:
 * - QmDeve - https://github.com/QmDeve
 * - Ahmed Sbai - https://github.com/sbaiahmed1
 * ===========================================
 */

package com.qmdeve.blurview;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

public class BlurNative implements Blur {

    private static final int MAX_RADIUS = 25;
    private static final int MIN_RADIUS = 2;
    private static final int THREAD_COUNT;
    private static final ExecutorService EXECUTOR;

    static {
        int cpuCount = Runtime.getRuntime().availableProcessors();
        THREAD_COUNT = Math.max(2, Math.min(5, cpuCount));
        EXECUTOR = Executors.newFixedThreadPool(THREAD_COUNT, r -> {
            Thread t = new Thread(r, "NativeBlurThread");
            t.setPriority(Thread.MIN_PRIORITY);
            t.setDaemon(true);
            return t;
        });
        System.loadLibrary("QmBlur");
    }

    private final AtomicBoolean isBlurring = new AtomicBoolean(false);
    private float radius = MAX_RADIUS;

    public static native void blur(
            Object bitmap,
            int radius,
            int threadCount,
            int threadIndex,
            int round
    );

    @Override
    public boolean prepare(Bitmap buffer, float radius) {
        this.radius = clamp(radius);
        return true;
    }

    @Override
    public void release() {
        // Shared executor, do not shutdown
    }

    @Override
    public void blur(Bitmap input, Bitmap output) {
        if (input == null || output == null ||
                input.isRecycled() || output.isRecycled()) return;

        if (!isBlurring.compareAndSet(false, true)) return;

        try {
            if (input != output) {
                // Clear the output bitmap to ensure no previous content remains
                // This is important when bitmaps are reused from pools
                output.eraseColor(0);
                new Canvas(output).drawBitmap(input, 0, 0, null);
            }
            doBlurRound(output, 1);
            doBlurRound(output, 2);
        } catch (Exception e) {
            // Only print stack trace if debug mode is enabled
            // Note: DEBUG may be null if Context was never provided
            if (Boolean.TRUE.equals(DEBUG)) e.printStackTrace();
        } finally {
            isBlurring.set(false);
        }
    }

    private void doBlurRound(Bitmap bitmap, int round) {
        int r = (int) radius;
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

        for (int i = 0; i < THREAD_COUNT; i++) {
            final int index = i;
            EXECUTOR.execute(() -> {
                try {
                    blur(bitmap, r, THREAD_COUNT, index, round);
                } catch (Exception e) {
                    // Only print stack trace if debug mode is enabled
                    // Note: DEBUG may be null if Context was never provided
                    if (Boolean.TRUE.equals(DEBUG)) e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            });
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private static float clamp(float value) {
        return Math.max((float) BlurNative.MIN_RADIUS, Math.min((float) BlurNative.MAX_RADIUS, value));
    }

    private static Boolean DEBUG = null;

    static boolean isDebug(Context ctx) {
        if (DEBUG == null && ctx != null) {
            DEBUG = (ctx.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
        }
        return Boolean.TRUE.equals(DEBUG);
    }
}